#pragma once

#include <QGLWidget>
#include <QVector>
#include <QColor>
#include <util/linear_transform.h>

namespace util { namespace gl {

  struct color {
    static const color black;
    static const color white;

    color(GLubyte r = 0, GLubyte g = 0, GLubyte b = 0)
    : r(r)
    , g(g)
    , b(b)
    {
    }
    union {
      struct {
        GLubyte r, g, b;
      };
      GLubyte c[3];
    };
  };

  struct matrix_renderer {
    bool light;
    bool fill;
    bool cross_lines;
    bool long_lines;
    long line_width;
    QVector<color> palette;
    QVector<color> line_colors;
  };

  struct axis {
    QString caption;
    GLdouble vector_begin[3];
    GLdouble vector_end[3];
    LinearTransform value_transform; // 0->1
    qreal value_step;
  };

  inline void calc_normal(float dzx, float dzy, float d, GLfloat n[])
  {
    n[0] = d * dzx;
    n[1] = -d * dzy;
    n[2] = d * d;
  }
  template<class MATRIX>
  inline void draw_matrix(const MATRIX &m, const matrix_renderer &renderer)
  {
    typedef MATRIX matrix_t;
    assert(renderer.fill || renderer.cross_lines || renderer.long_lines);
    matrix_t::value_type val;
    QColor color;

    glLineWidth(GLfloat(renderer.line_width));

    glEnable(GL_DEPTH_TEST);
    const size_t rows = m.size1();
    const size_t cols = m.size2();
    if (renderer.fill)
    {
      GLfloat n[3];
      for (size_t col = 0; col < cols - 1; col++)
      {
        glBegin(GL_QUAD_STRIP);
        for (size_t row = 0; row < rows; row++)
        {
          {
            const matrix_t::value_type &val = m(row, col);
            glColor3ubv(renderer.palette[val].c);
            if (renderer.light)
            {
              if (row < rows - 1)
              {
                calc_normal(float(m(row+1, col)) - float(val), float(m(row, col+1)) - float(val), 1.0f, n);
                glNormal3fv(n);
              }
            }
            glVertex3f(float(row), float(col), float(val));
          }

          {
            const matrix_t::value_type &val = m(row, col + 1);
            //          val = m(row, col+1);
            glColor3ubv(renderer.palette[val].c);
            if (renderer.light)
            {
              if (row < rows - 1 && col < cols - 2)
              {
                calc_normal(float(m(row+1, col+1)) - float(val), float(m(row, col+2)) - float(val), 1.0f, n);
                glNormal3fv(n);
              }
            }
            glVertex3f(float(row), float(col+1), float(val));
          }
        }
        glEnd();
      }
    }
    glDisable(GL_DEPTH_TEST);
    //  glEnable(GL_POLYGON_OFFSET_LINE);
    //  glEnable(GL_POLYGON_OFFSET_FILL);
    glPolygonOffset(-1,-1);
    const size_t line_colors_qty = renderer.line_colors.size();
    if (renderer.cross_lines)
    {
      if (line_colors_qty > 0)
      {
        for (size_t row = 0; row < rows; ++row)
        {
          glColor3ubv(renderer.line_colors[row % line_colors_qty].c);
          glBegin(GL_LINE_STRIP);
          for (size_t col = 0; col < cols; ++col)
          {
            const matrix_t::value_type &val = m(row, col);
            glVertex3f(float(row), float(col), float(val));
          }
          glEnd();
        }
      }
      else
      {
        for (size_t row = 0; row < rows; ++row)
        {
          glBegin(GL_LINE_STRIP);
          for (size_t col = 0; col < cols; ++col)
          {
            const matrix_t::value_type &val = m(row, col);
            glColor3ubv(renderer.palette[val].c);
            glVertex3f(float(row), float(col), float(val));
          }
          glEnd();
        }
      }
    }
    if (renderer.long_lines)
    {
      if (line_colors_qty > 0)
      {
        for (size_t col = 0; col < cols; ++col)
        {
          glColor3ubv(renderer.line_colors[col % line_colors_qty].c);
          glBegin(GL_LINE_STRIP);
          for (size_t row = 0; row < rows; ++row)
          {
            const matrix_t::value_type &val = m(row, col);
            glVertex3f(float(row), float(col), float(val));
          }
          glEnd();
        }
      }
      else
      {
        for (size_t col = 0; col < cols; ++col)
        {
          glBegin(GL_LINE_STRIP);
          for (size_t row = 0; row < rows; ++row)
          {
            const matrix_t::value_type &val = m(row, col);
            glColor3ubv(renderer.palette[val].c);
            glVertex3f(float(row), float(col), float(val));
          }
          glEnd();
        }
      }
    }
    //  glDisable(GL_POLYGON_OFFSET_LINE);
    //  glDisable(GL_POLYGON_OFFSET_FILL);
    glLineWidth(1);
  }

  void draw_axis(const axis &, QGLWidget *, const QFont &font = QFont());

} } // namespace util { namespace gl {